// Copyright (c)  MOKEYISH. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Text;
using NSQL.Expressions;
using NSQL.Expressions.Functions;
using NSQL.LogicalResource;
using NSQL.Statements;

namespace NSQL.Generator
{
    public class SqlGenerator : BaseGenerator, IExpressionVisitor<StringBuilder>
    {
        public static readonly ISqlGenerator Instance = new SqlGenerator(new SqlGeneratorSettings
        {
            Optimized = false
        });
        public SqlGenerator(SqlGeneratorSettings settings) : base(settings)
        {
        }
        
        protected virtual string Contains => Format(nameof(Contains));
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ITrimFunction trimFunction)
        {
            var  builder =  new StringBuilder();
            builder.Append(Trim);
            switch (trimFunction.Type)
            {
                case TrimType.Both:
                    break;
                case TrimType.Leading:
                    builder.Append('_').Append(Start);
                    break;
                case TrimType.Trailing:
                    builder.Append('_').Append(End);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append('(');
            builder.Append(trimFunction.Expr.Accept(this));
            if (!(trimFunction.TrimExpr is null))
            {
                builder.Append(", ");
                builder.Append(trimFunction.TrimExpr?.Accept(this));
            }
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IAddIntervalFunction addIntervalFunction)
        {
            var builder = new StringBuilder();
            builder.Append("Add");
            switch (addIntervalFunction.Interval.Type)
            {
                case IntervalType.Years:
                    builder.Append(nameof(IntervalType.Years));
                    break;
                case IntervalType.Months:
                    builder.Append(nameof(IntervalType.Months));
                    break;
                case IntervalType.Days:
                    builder.Append(nameof(IntervalType.Days));
                    break;
                case IntervalType.Hours:
                    builder.Append(nameof(IntervalType.Hours));
                    break;
                case IntervalType.Minutes:
                    builder.Append(nameof(IntervalType.Minutes));
                    break;
                case IntervalType.Seconds:
                    builder.Append(nameof(IntervalType.Seconds));
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append('(');
            builder.Append(addIntervalFunction.DateTime.Accept(this));
            builder.Append(", ").Append(addIntervalFunction.Interval.Interval);
            builder.Append(')');
            return builder;
        }


        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IExtractFunction extractFunction)
        {
            var builder = new StringBuilder();
            switch (extractFunction.Type)
            {
                case ExtractType.Year:
                    builder.Append(Year);
                    break;
                case ExtractType.Month:
                    builder.Append(Month);
                    break;
                case ExtractType.Day:
                    builder.Append(Day);
                    break;
                case ExtractType.Hour:
                    builder.Append(Hour);
                    break;
                case ExtractType.Minute:
                    builder.Append(Minute);
                    break;
                case ExtractType.Second:
                    builder.Append(Second);
                    break;
                case ExtractType.Week:
                    builder.Append(Week);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            return builder.Append('(').Append(extractFunction.DateTime.Accept(this)).Append(')');
        }
        
        StringBuilder IExpressionVisitor<StringBuilder>.Visit(IColumnExpression columnExpression)
        {
            var builder = new StringBuilder();
            var columnName = columnExpression.Name;
            if (columnExpression.Id > 0)
            {
                var column = LogicalDatabase?.FindColumnById(columnExpression.Id);
                if (column != null)
                {
                    columnName = column.Name;
                }
            }

            builder.Append('[');
            if (columnExpression.Delimited)
            {
                builder.Append('"');
                builder.Append(columnName);
                builder.Append('"');
            }
            else
            {
                builder.Append(columnName);
            }

            builder.Append(']');
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IStartWithFunction startWithFunction)
        {
            var builder = new StringBuilder();
            builder.Append(Start).Append('_').Append(With);
            builder.Append('(');
            builder.Append(startWithFunction.Expr.Accept(this));
            builder.Append(", ");
            builder.Append(startWithFunction.Pattern.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IEndWithFunction endWithFunction)
        {  
            var builder = new StringBuilder();
            builder.Append(End).Append('_').Append(With);
            builder.Append('(');
            builder.Append(endWithFunction.Expr.Accept(this));
            builder.Append(", ");
            builder.Append(endWithFunction.Pattern.Accept(this));
            builder.Append(')');
            return builder;
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IContainsFunction containsFunction)
        {  
            var builder = new StringBuilder();
            builder.Append(Contains);
            builder.Append('(');
            builder.Append(containsFunction.Expr.Accept(this));
            builder.Append(", ");
            builder.Append(containsFunction.Pattern.Accept(this));
            builder.Append(')');
            return builder;
        }
    }
    
    public static class SqlGeneratorExtensions
    {
        public static string ToSql(this IStatement statement, SqlGeneratorSettings settings = null)
        {
            return new SqlGenerator(settings ?? new SqlGeneratorSettings()).Generate(statement);
        }
        public static string ToSql(this IStatement statement, ILogicalDatabase database)
        {
            return new SqlGenerator(new SqlGeneratorSettings()
            {
                Database = database
            }).Generate(statement);
        }
    }
}